// ## 类装饰器
type ClassDecorator2 = <TFunction extends Function>(
  target: TFunction
) => TFunction | void;
// 类装饰器可以没有返回值，如果有返回值，就会替代所装饰的类的构造函数

/* function f(target: any) {
  console.log("target-", target);
  return target;
}

@f
class A {}
// 类A不需要新建实例，装饰器也会执行。装饰器会在代码加载阶段执行，而不是在运行时执行，而且只会执行一次

function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class BugReport {
  type = "report";
  title!: string;

  constructor(t: string) {
    this.title = t;
  }
}
const aa = new BugReport("00");
console.log(aa);
Reflect.deleteProperty(aa, "type");
console.log(aa); */

/* function factory(info: string) {
  console.log("received: ", info);
  return function (target: any) {
    console.log("apply decorator", target);
    return target;
  };
}

@factory("log something")
class A {} */

type Constructor = {
  new (...args: any[]): {};
};

function decoratorClass<T extends Constructor>(target: T) {
  return class extends target {
    value = 1236;
  };
}

@decoratorClass
class Foo {
  value = 456;
}

const foo = new Foo();
// console.log(foo.value); // 1236

// ## 方法装饰器
type MethodDecorator2 = <T>(
  target: Object,
  propertyKey: string | symbol,
  descriptor: TypedPropertyDescriptor<T>
) => TypedPropertyDescriptor<T> | void;
// 方法装饰器一共可以接受三个参数。
// target：（对于类的静态方法）类的构造函数，或者（对于类的实例方法）类的原型。
// propertyKey：所装饰方法的方法名，类型为string|symbol。
// descriptor：所装饰方法的描述对象。

function enumerable(value: boolean) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    console.log("target-", target, propertyKey);
    console.log(Object.getOwnPropertyNames(target), Reflect.ownKeys(target));
    descriptor.enumerable = value;
  };
}
class Greeter {
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }

  @enumerable(false)
  greet() {
    return "Hello, " + this.greeting;
  }
  static greeting() {
    return 2;
  }
  @enumerable(false)
  static greet() {
    return "Hello, " + this.greeting;
  }
  getAge() {
    return 28;
  }
}
const g1 = new Greeter("kk");
// 类的内部所有定义的方法，都是不可枚举的（non-enumerable）
console.log(Object.keys(Greeter.prototype)); // []
console.log(
  Object.getOwnPropertyNames(Greeter.prototype),
  Reflect.ownKeys(Greeter.prototype)
);
// console.log(g1, Object.keys(g1));

// ## 属性装饰器
type PropertyDecorator2 = (
  target: Object,
  propertyKey: string | symbol
) => void;
// 属性装饰器函数接受两个参数。
// target：（对于实例属性）类的原型对象（prototype），或者（对于静态属性）类的构造函数。
// propertyKey：所装饰属性的属性名，注意类型有可能是字符串，也有可能是 Symbol 值。
console.log("-----------------------------------------");
function ValidRange(min: number, max: number) {
  return (target: Object, key: string) => {
    console.log(target, key, Object.keys(target));
    console.log(
      Object.getOwnPropertyNames(target),
      Reflect.ownKeys(target),
      Object.getOwnPropertyDescriptor(target, key)
    );
    Object.defineProperty(target, key, {
      set: function (v: number) {
        if (v < min || v > max) {
          throw new Error(`Not allowed value ${v}`);
        }
      }
    });

    console.log(Object.getOwnPropertyDescriptor(target, key));
  };
}

// 输出 Installing ValidRange on year
class Student {
  @ValidRange(1920, 2020)
  year!: number;

  constructor() {
    this.year = 2000;
  }
  getYear() {
    console.log(this.year);
  }
}
const stud = new Student();
// 报错 Not allowed value 2022
stud.year = 2019;
console.log(Object.getOwnPropertyNames(Student.prototype), Object.keys(stud));

// 如果属性装饰器设置了当前属性的存取器（getter/setter），然后在构造函数里面就可以对实例属性进行读写。
function Min(limit: number) {
  return function (target: Object, propertyKey: any) {
    /* console.log(
      Reflect.getOwnPropertyDescriptor(target, propertyKey),
      propertyKey,
      Object.keys(target)
    ); */
    let value: string;

    const getter = function () {
      return value;
    };

    const setter = function (newVal: string) {
      if (newVal.length < limit) {
        throw new Error(`Your password should be bigger than ${limit}`);
      } else {
        value = newVal;
      }
    };

    Object.defineProperty(target, propertyKey, {
      get: getter,
      set: setter
    });
  };
}
class User {
  username: string;
  @Min(8)
  password: string;

  constructor(username: string, password: string) {
    this.username = username;
    this.password = password;
  }
}
const u = new User("Foo", "zhaoandong");

// ## 存取器装饰器
type AccessorDecorator = <T>(
  target: Object,
  propertyKey: string | symbol,
  descriptor: TypedPropertyDescriptor<T>
) => TypedPropertyDescriptor<T> | void;
// 存取器装饰器用来装饰类的存取器（accessor）。所谓“存取器”指的是某个属性的取值器（getter）和存值器（setter）。
// 存取器装饰器的类型定义，与方法装饰器一致。

function configurable(value: boolean) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    descriptor.configurable = value;
  };
}
class Point {
  private _x: number;
  private _y: number;

  constructor(x: number, y: number) {
    this._x = x;
    this._y = y;
  }

  @configurable(false)
  get x() {
    return this._x;
  }

  @configurable(false)
  get y() {
    return this._y;
  }
}
const p = new Point(1, 2);
// console.log(Reflect.ownKeys(p), p.x, Object.keys(p));

function validator(
  target: Object,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalSet = descriptor.set;

  if (originalSet) {
    descriptor.set = function (val) {
      if (val > 100) {
        throw new Error(`Invalid value(${val}) for ${propertyKey}`);
      }
      originalSet.call(this, val);
    };
  }
}

class C {
  #foo!: number;

  get foo() {
    return this.#foo;
  }

  @validator
  set foo(v) {
    this.#foo = v;
  }
}
const c = new C();
c.foo = 15;

// ## 参数装饰器
// 参数装饰器用来装饰构造方法或者其他方法的参数
/* type ParameterDecorator2 = (
  target: Object,
  propertyKey: string | symbol,
  parameterIndex: number
) => void;

function logParameter(
  target: Object,
  propertyKey: string | symbol,
  parameterIndex: number
) {
  console.log(target);
  console.log(`${String(propertyKey)} NO.${parameterIndex} Parameter`);
}
class D {
  member(@logParameter x: number, @logParameter y: number) {
    console.log(`member Parameters: ${x} ${y}`);
  }
}

const d = new D();
d.member(5, 7); */

export {};
